﻿using System;
using System.Text;
using System.Web.UI;
using CKS.EBE.API;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace CKS.EBE.Linkbacks
{
    public class Trackback : Page
    {
        private bool _SpamDetected;
        private bool _AkismetApproved;
        
        // When there are a lot of items in a list, SPList.Items.Add can get very slow. Using SPWeb.ProcessBatchData():
        private const string AddReceivedTrackbackCamlBatchFormat =
            @"<?xml version=""1.0"" encoding=""UTF-8""?>
            <ows:Batch>
            <Method ID=""B1"">
            <SetList>{0}</SetList>
            <SetVar Name=""ID"">New</SetVar>
            <SetVar Name=""Cmd"">Save</SetVar>
            <SetVar Name=""urn:schemas-microsoft-com:office:office#PostTitle"">{1}</SetVar>
            <SetVar Name=""urn:schemas-microsoft-com:office:office#LinkbackType"">Trackback</SetVar>
            <SetVar Name=""urn:schemas-microsoft-com:office:office#BlogName"">{2}</SetVar>
            <SetVar Name=""urn:schemas-microsoft-com:office:office#LinkingPost"">{3}, {4}</SetVar>
            <SetVar Name=""urn:schemas-microsoft-com:office:office#LinkingPostExcerpt"">{5}</SetVar>
            </Method>
            </Batch>"; // {0} = Linkbacks Received list GUID, {1} = local post lookup value (ID number??),
        // {2} = Name of "linking" blog, {3} = URL of linking post, {4} = title of linking post, 
        // {5} = Excerpt from linking blog post

        private int _ErrorCode;
        private string _ErrorMessage = String.Empty;

        // Need to keep these variables as private members for argument-less CodeToRunElevated delegate:
        private string _WebUrl, _LinkingPostUrl, _LinkingBlogName, _LinkingPostTitle, _LinkingPostExcerpt;
        private int _PostId;

        private void ProcessThisPage()
        {
            Response.ClearContent();
            Response.ContentType = "text/xml";
            Response.ContentEncoding = Encoding.UTF8;

            try
            {
                if (BlogContext.Current.Settings.TrackbackReceiptEnabled == false)
                {
                    SetError("The owner of the blog has chosen not to receive trackbacks.");
                    return;
                }
            }
            catch (Exception ex)
            {
                Trace.Write("Error checking TrackbackReceiptEnabled: " + ex);
            }

            try
            {
                _LinkingPostUrl = Request.Form["url"];
                bool hasPostId = int.TryParse(Request.QueryString["ID"], out _PostId);
                if (hasPostId == false || string.IsNullOrEmpty(_LinkingPostUrl))
                {
                    SetError("You are likely viewing this error code because you put the trackback ping URL into your Internet browser. Instead, you must enter the trackback ping URL in the appropriate field within your blogging application.");
                    return;
                }
            }
            catch (Exception ex)
            {
                Trace.Write("Error getting url or id from QueryString: " + ex);
            }

            try
            {
                _WebUrl = SPControl.GetContextWeb(Context).Url; // need to store URL and then get web instance from it in the RegisterTrackbackPing method for elevated context
                _LinkingBlogName = Convert.ToString(Request.Form["blog_name"]); // optional, so empty string is OK
                _LinkingPostTitle = Convert.ToString(Request.Form["title"]); // optional
                _LinkingPostExcerpt = Convert.ToString(Request.Form["excerpt"]); // optional
            }
            catch (Exception ex)
            {
                Trace.Write("Error getting context: " + ex);
            }

            SPWeb web = SPContext.Current.Web;
            string localPostUrl = string.Format("{0}/Lists/{1}/ViewPost.aspx?ID={2}",
                web.Url, Localization.GetListName(Localization.BlogList.Posts, web.Language), _PostId);
            _SpamDetected = Helper.DetectSpam(localPostUrl, _LinkingPostUrl, _PostId, Page.Request, out _AkismetApproved, ref _LinkingPostTitle);

            if (!_SpamDetected)
            {
                try
                {
                    SPSecurity.CodeToRunElevated codeToRunElevated = RegisterTrackbackPing;
                    SPSecurity.RunWithElevatedPrivileges(codeToRunElevated);
                }
                catch (Exception ex)
                {
                    Trace.Write("Error registering Trackback ping: " + ex);
                    SetError("An unknown error occurred");
                    return;
                }
            }
        }

        private void RegisterTrackbackPing()
        {
            using (var site = new SPSite(_WebUrl))
            using (SPWeb web = site.OpenWeb()) // the web from the current context will not have elevated priveleges
            {
                web.AllowUnsafeUpdates = true;

                // Add a received trackback list item:
                SPList linkbacksReceivedList = null;
                try
                {
                    linkbacksReceivedList = web.Lists[Globals.LinkbacksReceivedListName];
                }
                catch (Exception ex)
                {
                    Trace.Write("Error getting Linkbacks Received List: " + ex);
                }
                if (linkbacksReceivedList == null)
                {
                    SetError("The blog you are pinging is not configured correctly for receiving trackbacks.");
                    return;
                }

                // Query the list to see whether a trackback ping was already registered:
                var duplicateQuery = new SPQuery
                                         {
                                             Query = string.Format(Globals.QueryForDuplicateReceivedLinkback, _PostId, Helper.XmlEscape(_LinkingPostUrl)),
                                             RowLimit = 1
                                         };
                SPListItemCollection duplicateReceivedLinkbackItems = linkbacksReceivedList.GetItems(duplicateQuery);
                if (duplicateReceivedLinkbackItems != null && duplicateReceivedLinkbackItems.Count > 0)
                {
                    SetError(string.Format("You have already sent a trackback ping to {0} for the post at URL {1}.", Page.Request.RawUrl, _LinkingPostUrl));
                    return;
                }

                string linkbacksReceivedListGuid = linkbacksReceivedList.ID.ToString();

                string addTrackbackPingItemCaml = string.Format(AddReceivedTrackbackCamlBatchFormat,
                                                                linkbacksReceivedListGuid,
                                                                _PostId,
                                                                Helper.XmlEscape(_LinkingBlogName),
                                                                Helper.XmlEscape(_LinkingPostUrl),
                                                                Helper.XmlEscape(_LinkingPostTitle),
                                                                Helper.XmlEscape(_LinkingPostExcerpt)
                    );

                string result = web.ProcessBatchData(addTrackbackPingItemCaml);
                Trace.Write("Add Trackback result: " + result);

                if (_AkismetApproved && !string.IsNullOrEmpty(BlogContext.Current.Settings.AkismetKey) && linkbacksReceivedList.EnableModeration ||
                    string.IsNullOrEmpty(BlogContext.Current.Settings.AkismetKey) && linkbacksReceivedList.EnableModeration)
                {
                    // the linkback is akismet approved, or considered as spamfree
                    var query = new SPQuery
                                    {
                                        Query = string.Format(Globals.QueryForDuplicateReceivedLinkback, _PostId, Helper.XmlEscape(_LinkingPostUrl))
                                    };
                    SPListItemCollection items = linkbacksReceivedList.GetItems(query);
                    if (items.Count == 1)
                    {
                        // get item instead of modifying the one from the collection, because that way we can set the moderation status
                        var itemToApprove = linkbacksReceivedList.GetItemById(items[0].ID);
                        itemToApprove.ModerationInformation.Status = SPModerationStatusType.Approved;
                        itemToApprove.UpdateOverwriteVersion();
                    }
                }

                // TODO: Log any errors that may have occurred
            }
        }

        private void SetError(string message)
        {
            _ErrorCode = 1;
            _ErrorMessage = message;
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            ProcessThisPage();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (_SpamDetected)
            {
                // hide the response to a spammer
                Response.SuppressContent = true;
                Response.Close();
            }
            else
            {
                base.Render(writer);

                Response.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
                Response.Write("<response>");
                Response.Write("<error>" + _ErrorCode + "</error>");
                if (string.IsNullOrEmpty(_ErrorMessage) == false)
                {
                    Response.Write(String.Format("<message>{0}</message>", Helper.XmlEscape(_ErrorMessage)));
                }
                Response.Write("</response>");
            }
        }
    }
}
